package com.rgbgame.game;

import processing.core.PApplet;
import java.awt.Color;

/**
 * @author Will
 * @date Mar 4, 2008 - 2:32:52 PM
 */
public class SolidWall2d {

    public static rgbGame g = rgbGame.game;
    public static PApplet p = rgbGame.pApplet;

    public static int lastID = 0;

    Vector3d bufferE = new Vector3d(0, 0, 0); // reserved for com.rgbgame.game.SolidWall2d
    // class
    Vector3d bufferF = new Vector3d(0, 0, 0); // reserved for com.rgbgame.game.SolidWall2d
    // class
    Vector3d bufferG = new Vector3d(0, 0, 0); // reserved for com.rgbgame.game.SolidWall2d
    // class
    Vector3d bufferH = new Vector3d(0, 0, 0); // reserved for com.rgbgame.game.SolidWall2d
    // class

    public static final int NORMALWALL = 0;
    public static final int DEATHWALL = 1;
    public static final int STICKYWALL = 2;

    // Has exclusive access to com.rgbgame.game.Vector3d vars bufferE and bufferF
    // Currently assumes z=constant
    public Vector3d wallA;
    public Vector3d wallB;
    public float restitutionCoefficient; // 0 is no bounce, 1 is perfect bounce
    public float frictionCoefficient; // 1 is sticky, 0 is slippery
    public Color wallColor;
    public int idNum;
    private boolean skipFlag;
    private Vector3d origV;
    public int typeFlag;

    {
        restitutionCoefficient = 0.0f;
        frictionCoefficient = .99f;
        wallColor = Color.white;
        idNum = ++lastID;
        skipFlag = false;
        typeFlag = NORMALWALL;
    }

    public SolidWall2d(Vector3d a, Vector3d b) {
        wallA = new Vector3d(a);
        wallB = new Vector3d(b);
    }

    public SolidWall2d(float x1, float y1, float x2, float y2) {
        wallA = new Vector3d(x1, y1, 0);
        wallB = new Vector3d(x2, y2, 0);
    }

    public SolidWall2d(Vector3d a, Vector3d b, float _restCoef,
                       float _fricCoef, Color _wallColor) {
        wallA = new Vector3d(a);
        wallB = new Vector3d(b);
        restitutionCoefficient = _restCoef;
        frictionCoefficient = _fricCoef;
        wallColor = _wallColor;
    }

    public void draw(Color customColor) {

        float factor = 2 * PApplet.max(rgbGame.width, rgbGame.height);
        if ((-rgbGame.game.cameraX < (PApplet.min(wallA.x, wallB.x) - factor))
                || (-rgbGame.game.cameraX > (PApplet.max(wallA.x, wallB.x) + factor))) {
            return;
        }
        if ((-rgbGame.game.cameraY < (PApplet.min(wallA.y, wallB.y) - factor))
                || (-rgbGame.game.cameraY > (PApplet.max(wallA.y, wallB.y) + factor))) {
            return;
        }
        p.strokeWeight(3);
        p.stroke(customColor.getRed(), customColor.getGreen(), customColor.getBlue());
        p.fill(customColor.getRed(), customColor.getGreen(), customColor.getBlue());
        // strokeWeight(4);
        if (rgbGame.drawing3d) {
            p.line(wallA.x, wallA.y, wallA.z, wallB.x, wallB.y, wallB.z);
            p.noStroke();
//            p.ellipse(wallA.x, wallA.y, 3, 3);
//            p.ellipse(wallB.x, wallB.y, 3, 3);
        } else {
            p.line(wallA.x, wallA.y, wallB.x, wallB.y);
            p.noStroke();
//            p.ellipse(wallA.x, wallA.y, 3, 3);
//            p.ellipse(wallB.x, wallB.y, 3, 3);
        }
        p.strokeWeight(1);
        p.noStroke();
    }

    public void draw(rgbGame g) {
        if (skipFlag) {
            skipFlag = false;
            return;
        } else {
            draw(wallColor);
        }
    }

    public void skipNextDraw() {
        skipFlag = true;
    }

    private boolean colliding(Particle particle, Vector3d collisionPoint) {
        // debugCounter++;
        // formula from
        // http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
        // This is a line-cross test, which might not be the best one to
        // use.

        float x1 = particle.position.x;
        float y1 = particle.position.y;
        float x2 = particle.truePositionLast.x;
        float y2 = particle.truePositionLast.y;
        float x3 = wallA.x;
        float y3 = wallA.y;
        float x4 = wallB.x;
        float y4 = wallB.y;

        float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3));
        float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3));
        float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
        if (PApplet.abs(denom) < .00001f) {
            // println("Oh crap");
            return false;
        }
        ua /= denom;
        ub /= denom;

        if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1)) {
            collisionPoint.x = (x1 + ua * (x2 - x1));
            collisionPoint.y = (y1 + ua * (y2 - y1));
            collisionPoint.z = 0;
            return true;
        }

        return false;
    }

    /**
     * NOTE: During main simulation, use collideTest to handle wall
     * collisions. However, if you are iteratively solving constraints,
     * collideTest should not be used more than once per step, preferably
     * near the beginning of the simulation step. Instead, use the following
     * algorithm: loop{ //main sim. loop for each particle:
     * collideTest(particle, collisionPt); for (i=0; i<constraintIters;
     * i++){ applyNonWallConstraintsToParticles(); for each particle:
     * projectCollide(particle, collisionPt); } } Note that within
     * constraint loop, projectCollide is used in lieu of collideTest!!! If
     * you use collideTest more than once per time step, you'll get pwned.
     * You're warned.
     */

    public boolean collideTest(Particle particle, Vector3d collisionPoint, rgbGame g) {
        // Now clean of com.rgbgame.game.Vector3d creation except for player particles
        // if (particle.isPlayer()) debugString += " id: "+idNum+"; ";
        if (particle.pinned)
            return false;

        float factor = 2.0f * particle.radius;
        float maxX = PApplet.max(particle.position.x, particle.truePositionLast.x);
        float maxY = PApplet.max(particle.position.y, particle.truePositionLast.y);
        float minX = PApplet.min(particle.position.x, particle.truePositionLast.x);
        float minY = PApplet.min(particle.position.y, particle.truePositionLast.y);
        float leftX = PApplet.min(wallA.x, wallB.x);
        float rightX = PApplet.max(wallA.x, wallB.x);
        float topY = PApplet.min(wallA.y, wallB.y);
        float bottomY = PApplet.max(wallA.y, wallB.y);
        if ((maxX < leftX - factor) || (minX > rightX + factor)) {
            return false;
        }
        if ((maxY < topY - factor) || (minY > bottomY + factor)) {
            return false;
        }
        if ((rightX - leftX) < 0.01f && (bottomY - topY) < 0.01f) {
            return false;
        }

        Vector3d myDisp = bufferG;
        particle.truePositionLast.displacementToLineSet(wallA, wallB,
                myDisp);
        // if (myDisp.norm()<.01&&!particle.isPlayer())
        // println(myDisp.norm());
        myDisp.normalize();
        myDisp.multiply(particle.radius);
        float pxin = particle.position.x;
        float pyin = particle.position.y;
        particle.position.add(myDisp);
        if (!colliding(particle, collisionPoint)) {
            particle.position.set(pxin, pyin, 0);
            if (checkEdges(particle)) {
                if (particle.isPlayer()) {
                    if (typeFlag == DEATHWALL)
                        g.myPlayer.explodeAndKill();
                }
                return true;
            }
            return false;
        }

        particle.position.set(pxin, pyin, 0);
        if (particle.isPlayer()) {
            // println("Collision with wall "+this.idNum);
            if (origV == null)
                origV = new Vector3d(0, 0, 0);
            origV.set(particle.position);
            origV.subtract(particle.positionLast);
        }
        float wax = wallA.x;
        float way = wallA.y;
        wallA.subtract(myDisp);
        collisionPoint.subtract(myDisp);
        reflectAndFriction(particle.position, wallA, collisionPoint,
                restitutionCoefficient, frictionCoefficient);
        reflectAndFriction(particle.positionLast, wallA, collisionPoint,
                restitutionCoefficient, frictionCoefficient);
        wallA.set(wax, way, 0);
        collisionPoint.add(myDisp);
        if (particle.isPlayer()) {
            float deltaVSqr = particle.position
                    .minus(particle.positionLast).minus(origV)
                    .normSquared();
            // println(deltaVSqr);
            int numToBurst = PApplet.min((int) (1.5f * deltaVSqr), 50);
            float myA = PApplet.min(numToBurst * 255.0f / 10.0f, 255);
            // println(numToBurst);
            if (numToBurst > 1) {
                g.burstFXParticles(numToBurst, particle.position,
                        particle.position.minus(particle.positionLast),
                        particle.myColor, 3.0f * (myA / 255.0f), myA);
            }
            if (typeFlag == STICKYWALL && !particle.stuck && PApplet.abs(particle.myColor.getRed() - 100f) > 1f)
                particle.stickParticle(.6f);
        }
        if (particle.isPlayer()) {
            if (typeFlag == DEATHWALL) {
                g.burstFXParticles(500, g.myPlayer.particle[0].position,
                        new Vector3d(0, 0, 0), new Color(40, 40, 40),
                        10.1f, 1555.0f);
                g.myPlayer.explodeAndKill();
            }
        }

        return true;
    }

    public boolean projectCollide(Particle particle, Vector3d collisionPoint) {
        // Totally clean of temp vector creation

        // This handles a collision by merely projecting the current
        // position rather than altering the velocity.
        // This is crucial if we are implementing iterative constraints -
        // velocity should only be altered once

        if (particle.pinned)
            return false;

        float factor = 2.0f * particle.radius;
        float maxX = PApplet.max(particle.position.x, particle.truePositionLast.x);
        float maxY = PApplet.max(particle.position.y, particle.truePositionLast.y);
        float minX = PApplet.min(particle.position.x, particle.truePositionLast.x);
        float minY = PApplet.min(particle.position.y, particle.truePositionLast.y);
        float leftX = PApplet.min(wallA.x, wallB.x);
        float rightX = PApplet.max(wallA.x, wallB.x);
        float topY = PApplet.min(wallA.y, wallB.y);
        float bottomY = PApplet.max(wallA.y, wallB.y);
        if ((maxX < leftX - factor) || (minX > rightX + factor)) {
            return false;
        }
        if ((maxY < topY - factor) || (minY > bottomY + factor)) {
            return false;
        }
        if ((rightX - leftX) < 0.01f && (bottomY - topY) < 0.01f) {
            return false;
        }

        Vector3d myDisp = bufferG;
        particle.truePositionLast.displacementToLineSet(wallA, wallB,
                myDisp);
        myDisp.normalize();
        myDisp.multiply(particle.radius);
        float pxin = particle.position.x;
        float pyin = particle.position.y;
        particle.position.add(myDisp);
        if (!colliding(particle, collisionPoint)) {
            // particle.position.subtract(myDisp);
            particle.position.set(pxin, pyin, 0);
            return checkEdges(particle);
        }
        // particle.position.subtract(myDisp);
        particle.position.set(pxin, pyin, 0);
        float wax = wallA.x;
        float way = wallA.y;
        wallA.subtract(myDisp);
        collisionPoint.subtract(myDisp);
        reflectAndFriction(particle.position, wallA, collisionPoint, 0.0f,
                0.0f);
        reflectAndFriction(particle.positionLast, wallA, collisionPoint,
                -1.0f, frictionCoefficient);
        wallA.set(wax, way, 0);
        collisionPoint.add(myDisp);
        return true;
    }

    private void reflectAndFriction(Vector3d myPosition,
                                    Vector3d lineStart, Vector3d collisionPoint, float perpCoef,
                                    float parCoef) {
        // 8 temp vector creations, not counting any that happen in
        // projectAlong()
        /*
        * com.rgbgame.game.Vector3d diff = collisionPoint.minus(lineStart); com.rgbgame.game.Vector3d diff2 =
        * collisionPoint.minus(myPosition); com.rgbgame.game.Vector3d proj =
        * diff2.projectAlong(diff); com.rgbgame.game.Vector3d perp = diff2.minus(proj);
        * return
        * myPosition.plus(perp.times(1.0+perpCoef)).plus(proj.times(parCoef));
        */

        // Clean version - no created vectors
        bufferE.set(wallB);
        bufferE.subtract(lineStart); // use wallB instead of collidePoint
        // in case collidePoint is at
        // position of lineStart
        bufferF.set(collisionPoint);
        bufferF.subtract(myPosition); // actually should be collisionPoint
        float invMyNorm = 1.0f / bufferE.norm(); // BUG - this could be a
        // divide by zero!!!
        // Better to replace
        // collisionPoint in
        // bufferE with lineEnd,
        // since
        // lineStart->lineEnd is
        // guaranteed != 0 by
        // design [FIXED]
        float bx = bufferE.x * invMyNorm;
        float by = bufferE.y * invMyNorm;
        float thisdotb = bufferF.x * bx + bufferF.y * by;
        bufferE.set(bx * thisdotb, by * thisdotb, 0.0f);
        bufferF.subtract(bufferE);
        bufferF.multiply(1.0f + perpCoef);
        bufferE.multiply(parCoef);
        bufferE.add(bufferF);
        myPosition.add(bufferE);
    }

    public boolean checkEdges(Particle particle) {
        boolean wA = true;
        float toc = Vector3d.lineCircleIntersectTOC(particle.truePositionLast,
                particle.position, wallA, particle.radius);
        if (toc < -0.5f) {
            toc = Vector3d.lineCircleIntersectTOC(particle.truePositionLast,
                    particle.position, wallB, particle.radius);
            wA = false;
        }

        if (toc < -0.5f) return false;
        //get collision point
        float collx = toc * particle.position.x + (1.0f - toc) * particle.truePositionLast.x;
        float colly = toc * particle.position.y + (1.0f - toc) * particle.truePositionLast.y;
        float dx = (collx - (wA ? wallA.x : wallB.x)) / particle.radius;
        float dy = (colly - (wA ? wallA.y : wallB.y)) / particle.radius;
        float pcx = particle.position.x - collx;
        float pcy = particle.position.y - colly;
        float pdotd = pcx * dx + pcy * dy;
        particle.position.x -= (1.0f + restitutionCoefficient) * pdotd * dx;
        particle.position.y -= (1.0f + restitutionCoefficient) * pdotd * dy;

        //		particle.position.set(collx,colly,0f);

        return true;

        /*
       // From lineAthlete codebase, ported from Actionscript
       float distA = particle.position.distanceTo(wallA);
       float distB = particle.position.distanceTo(wallB);
       if (distA > particle.radius && distB > particle.radius) {
           return false;
       } // no collision
       else if (distA <= particle.radius) {// colliding with wallA endpoint
           float alpha = particle.radius / (distA + .0001f);
           float oneminusalpha = 1.0f - alpha;
           particle.position.x = wallA.x * oneminusalpha + alpha
           * particle.position.x;
           particle.position.y = wallA.y * oneminusalpha + alpha
           * particle.position.y;
       } else {// colliding with wallB endpoint
           float alpha = particle.radius / (distB + .0001f);
           float oneminusalpha = 1.0f - alpha;
           particle.position.x = wallB.x * oneminusalpha + alpha
           * particle.position.x;
           particle.position.y = wallB.y * oneminusalpha + alpha
           * particle.position.y;
       }
       return true;*/
    }


}
